﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace JeanFish.Data.Common
{
    public abstract class DbProviderServices : System.Data.Common.DbProviderServices
    {
        private Dictionary<string, Func<System.Data.Common.DbProviderManifest, System.Data.Common.CommandTrees.DbCommandTree, System.Data.Common.DbCommandDefinition>>
            createDbCommandDefinitionFuctions = new Dictionary<string, Func<System.Data.Common.DbProviderManifest, System.Data.Common.CommandTrees.DbCommandTree, System.Data.Common.DbCommandDefinition>>();

        protected abstract string ProviderName { get; }
        protected abstract string DefaultProviderName { get; }

        public virtual DbCommandDefinition CreateCommandDefinition(System.Data.Common.DbCommandDefinition commandDefinition
            ,System.Data.Common.CommandTrees.DbCommandTree commandTree)
        {
            return new DbCommandDefinition(commandDefinition, commandTree,
                (cmd, def) => new DbCommand(cmd, def));
        }

        public virtual System.Data.Common.DbProviderManifest CreateProviderManifest(string providerName, System.Data.Common.DbProviderManifest providerMaifest)
        {
            return new DbProviderManifest(this.ProviderName, providerName, providerMaifest);
        }

        protected override string GetDbProviderManifestToken(System.Data.Common.DbConnection connection)
        {
            DbConnection newConnection = (DbConnection)connection;
            System.Data.Common.DbConnection oldConnection = newConnection.Connection;
            System.Data.Common.DbProviderServices services = DbProviderServices.GetProviderServices(oldConnection);

            return newConnection.ProviderName + ";" + services.GetProviderManifestToken(oldConnection);
        }

        protected override System.Data.Common.DbProviderManifest GetDbProviderManifest(string manifestToken)
        {
            string providerName;
            string realToken;

            int index = manifestToken.IndexOf(";");
            if (index < 0)
            {
                realToken = manifestToken;
                providerName = this.DefaultProviderName;
            }
            else
            {
                providerName = manifestToken.Substring(0, index);
                realToken = manifestToken.Substring(index + 1);
            }

            System.Data.Common.DbProviderServices services = GetProviderServicesByName(providerName);
            System.Data.Common.DbProviderManifest manifest = services.GetProviderManifest(realToken);
            return this.CreateProviderManifest(providerName, manifest);
        }

        protected override System.Data.Common.DbCommandDefinition CreateDbCommandDefinition(System.Data.Common.DbProviderManifest providerManifest, System.Data.Common.CommandTrees.DbCommandTree commandTree)
        {
            var newManifest = (DbProviderManifest)providerManifest ;
            var createDbCommandDefinitionFunction = this.GetCreateDbCommandDefinitionFunction(newManifest.DecoratorProviderName);

            System.Data.Common.DbCommandDefinition definition = createDbCommandDefinitionFunction(newManifest.ProviderManifest, commandTree);
            return this.CreateCommandDefinition(definition, commandTree);
        }

        protected override void DbCreateDatabase(System.Data.Common.DbConnection connection, int? commandTimeout, System.Data.Metadata.Edm.StoreItemCollection storeItemCollection)
        {
            var oldConnection = ((DbConnection)connection).Connection;
            var services = System.Data.Common.DbProviderServices.GetProviderServices(oldConnection);
            services.CreateDatabase(oldConnection, commandTimeout, storeItemCollection);
        }
        protected override bool DbDatabaseExists(System.Data.Common.DbConnection connection, int? commandTimeout, System.Data.Metadata.Edm.StoreItemCollection storeItemCollection)
        {
            var oldConnection = ((DbConnection)connection).Connection;
            var services = System.Data.Common.DbProviderServices.GetProviderServices(oldConnection);
            return services.DatabaseExists(oldConnection, commandTimeout, storeItemCollection);
        }
        protected override void DbDeleteDatabase(System.Data.Common.DbConnection connection, int? commandTimeout, System.Data.Metadata.Edm.StoreItemCollection storeItemCollection)
        {
            var oldConnection = ((DbConnection)connection).Connection;
            var services = System.Data.Common.DbProviderServices.GetProviderServices(oldConnection);
            services.DeleteDatabase(oldConnection, commandTimeout, storeItemCollection);
        }
        protected override string DbCreateDatabaseScript(string providerManifestToken, System.Data.Metadata.Edm.StoreItemCollection storeItemCollection)
        {
            string providerName;
            string realToken;

            int index = providerManifestToken.IndexOf(';');
            if (index < 0)
            {
                providerName = this.DefaultProviderName;
                realToken = providerManifestToken;
            }
            else
            {
                providerName = providerManifestToken.Substring(0, index);
                realToken = providerManifestToken.Substring(index + 1);
            }

            System.Data.Common.DbProviderServices services = GetProviderServicesByName(providerName);
            return services.CreateDatabaseScript(realToken, storeItemCollection);
        }
        private static System.Data.Common.DbProviderServices GetProviderServicesByName(string providerName)
        {
            System.Data.Common.DbProviderFactory factory = System.Data.Common.DbProviderFactories.GetFactory(providerName);

            IServiceProvider serviceProvider = factory as IServiceProvider;

            System.Data.Common.DbProviderServices providerService = (System.Data.Common.DbProviderServices)serviceProvider.GetService(typeof(DbProviderServices));

            return providerService;
        }

        private Func<System.Data.Common.DbProviderManifest, System.Data.Common.CommandTrees.DbCommandTree, System.Data.Common.DbCommandDefinition>
            GetCreateDbCommandDefinitionFunction(string providerName)
        {
            Func<System.Data.Common.DbProviderManifest, System.Data.Common.CommandTrees.DbCommandTree, System.Data.Common.DbCommandDefinition> result;
            lock (this.createDbCommandDefinitionFuctions)
            {
                if (!this.createDbCommandDefinitionFuctions.TryGetValue(providerName, out result))
                {
                    System.Data.Common.DbProviderServices services = GetProviderServicesByName(providerName);
                    this.createDbCommandDefinitionFuctions[providerName] = services.CreateCommandDefinition;
                    result = services.CreateCommandDefinition;
                }
            }
            return result;
        }
    }
}
